/*
 * Copyright (c) 2022, IMMORTA Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 *
 * - Neither the name of IMMORTA Inc. nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*!
 * @file can_reg_access.c
 * @brief This file defines can register access functions
 */

/*******Includes***************************************************************/
#include "can_reg_access.h"

/*******Definitions************************************************************/
/* CAN FD extended data length DLC encoding */
#define CAN_DLC_VALUE_12_BYTES          9U
#define CAN_DLC_VALUE_16_BYTES          10U
#define CAN_DLC_VALUE_20_BYTES          11U
#define CAN_DLC_VALUE_24_BYTES          12U
#define CAN_DLC_VALUE_32_BYTES          13U
#define CAN_DLC_VALUE_48_BYTES          14U
#define CAN_DLC_VALUE_64_BYTES          15U

/*******Variables**************************************************************/

/*******Prototypes*************************************************************/
static uint32_t CAN_REG_CombineU8ToU32(const uint8_t* buffer);
#if defined (__clang__) && defined (__ARMCC_VERSION) && (__ARMCC_VERSION >= 6010050)
static inline __attribute__((optnone))void CAN_REG_SafetyRead(volatile uint32_t* readData, volatile uint32_t* safetyCheck, const volatile uint32_t* RbuffAddr);
#else
static inline void CAN_REG_SafetyRead(volatile uint32_t* readData, volatile uint32_t* safetyCheck, const volatile uint32_t* RbuffAddr);
#endif

/*******Private Functions******************************************************/
/*******************************************************************************
 *** Function Name : CAN_REG_CombineU8ToU32
 *** Description   : Combine 4 bytes to uint32
 ******************************************************************************/
static uint32_t CAN_REG_CombineU8ToU32(const uint8_t* buffer)
{
    uint32_t value = 0;

    value |= (uint32_t)buffer[3] << 24U;
    value |= (uint32_t)buffer[2] << 16U;
    value |= (uint32_t)buffer[1] << 8U;
    value |= (uint32_t)buffer[0];

    return value;
}

/*******Code*******************************************************************/

/*******************************************************************************
 *** Function Name : CAN_REG_ComputeDLCValue
 *** Description   : Compute DLC value from payload size
 ******************************************************************************/
uint8_t CAN_REG_ComputeDLCValue(uint8_t payloadSize)
{
    uint8_t retValue = 0xFFU;
    static const uint8_t payloadCode[65] = {
        /* 0, 1, 2, 3, 4, 5, 6, 7, 8, */
        0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U,
        /* 9 ~ 12 payload size have DLC code 12 bytes */
        CAN_DLC_VALUE_12_BYTES, CAN_DLC_VALUE_12_BYTES, CAN_DLC_VALUE_12_BYTES, CAN_DLC_VALUE_12_BYTES,
        /* 13 ~ 16 payload size have DLC code 16 bytes */
        CAN_DLC_VALUE_16_BYTES, CAN_DLC_VALUE_16_BYTES, CAN_DLC_VALUE_16_BYTES, CAN_DLC_VALUE_16_BYTES,
        /* 17 ~ 20 payload size have DLC code 20 bytes */
        CAN_DLC_VALUE_20_BYTES, CAN_DLC_VALUE_20_BYTES, CAN_DLC_VALUE_20_BYTES, CAN_DLC_VALUE_20_BYTES,
        /* 21 ~ 24 payload size have DLC code 24 bytes */
        CAN_DLC_VALUE_24_BYTES, CAN_DLC_VALUE_24_BYTES, CAN_DLC_VALUE_24_BYTES, CAN_DLC_VALUE_24_BYTES,
        /* 25 ~ 32 payload size have DLC code 32 bytes */
        CAN_DLC_VALUE_32_BYTES, CAN_DLC_VALUE_32_BYTES, CAN_DLC_VALUE_32_BYTES, CAN_DLC_VALUE_32_BYTES,
        CAN_DLC_VALUE_32_BYTES, CAN_DLC_VALUE_32_BYTES, CAN_DLC_VALUE_32_BYTES, CAN_DLC_VALUE_32_BYTES,
        /* 33 ~ 48 payload size have DLC code 48 bytes */
        CAN_DLC_VALUE_48_BYTES, CAN_DLC_VALUE_48_BYTES, CAN_DLC_VALUE_48_BYTES, CAN_DLC_VALUE_48_BYTES,
        CAN_DLC_VALUE_48_BYTES, CAN_DLC_VALUE_48_BYTES, CAN_DLC_VALUE_48_BYTES, CAN_DLC_VALUE_48_BYTES,
        CAN_DLC_VALUE_48_BYTES, CAN_DLC_VALUE_48_BYTES, CAN_DLC_VALUE_48_BYTES, CAN_DLC_VALUE_48_BYTES,
        CAN_DLC_VALUE_48_BYTES, CAN_DLC_VALUE_48_BYTES, CAN_DLC_VALUE_48_BYTES, CAN_DLC_VALUE_48_BYTES,
        /* 49 ~ 64 payload size have DLC code 64 bytes */
        CAN_DLC_VALUE_64_BYTES, CAN_DLC_VALUE_64_BYTES, CAN_DLC_VALUE_64_BYTES, CAN_DLC_VALUE_64_BYTES,
        CAN_DLC_VALUE_64_BYTES, CAN_DLC_VALUE_64_BYTES, CAN_DLC_VALUE_64_BYTES, CAN_DLC_VALUE_64_BYTES,
        CAN_DLC_VALUE_64_BYTES, CAN_DLC_VALUE_64_BYTES, CAN_DLC_VALUE_64_BYTES, CAN_DLC_VALUE_64_BYTES,
        CAN_DLC_VALUE_64_BYTES, CAN_DLC_VALUE_64_BYTES, CAN_DLC_VALUE_64_BYTES, CAN_DLC_VALUE_64_BYTES
    };

    if (payloadSize <= 64) {
        retValue = payloadCode[payloadSize];
    }

    return retValue;
}

/*******************************************************************************
 *** Function Name : CAN_REG_Computepayloadsize
 *** Description   : Compute payloadsize from dlc value
 ******************************************************************************/
uint8_t CAN_REG_Computepayloadsize(uint8_t dlcValue)
{
    uint8_t retValue = 0U;
    const uint8_t dlcCode[] = {
        0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U,
        8U, 12U, 16U, 20U, 24U, 32U, 48U, 64U
    };

    if (dlcValue < 0x10) {
        retValue = dlcCode[dlcValue];
    }

    return retValue;
}


/*******************************************************************************
 *** Function Name : CAN_REG_SetMode
 *** Description   : Set CAN mode
 ******************************************************************************/
void CAN_REG_SetMode(CAN_Type* regBase, can_mode_t mode)
{
    uint32_t regValue = regBase->CTRL0 & (~(CAN_CTRL0_TSNEXT_Msk | CAN_CTRL0_TSALL_Msk | CAN_CTRL0_TSONE_Msk | CAN_CTRL0_TPE_Msk));

    regValue &= ~(CAN_CTRL0_LOM_Msk | CAN_CTRL0_LBME_Msk | CAN_CTRL0_LBMI_Msk);

    switch (mode) {
        case CAN_NORMAL_MODE:
            break;

        case CAN_LISTEN_ONLY_MODE:
            regValue |= CAN_CTRL0_LOM(1U);
            break;

        case CAN_LOOPBACK_EXTERNAL_MODE:
            regValue |= CAN_CTRL0_LBME(1U);
            regValue |= CAN_CTRL0_SACK(1U);
            break;

        case CAN_LOOPBACK_INTERNAL_MODE:
            regValue |= CAN_CTRL0_LBMI(1U);
            break;

        default :
            break;
    }

    regBase->CTRL0 = regValue;
}

/*******************************************************************************
 *** Function Name : CAN_REG_SetTxMsg
 *** Description   : Fill tx message buffer
 ******************************************************************************/
void CAN_REG_SetTxMsg(CAN_Type* regBase,
                      can_buffer_type_t type,
                      const can_data_info_t* info,
                      uint32_t msgId,
                      const uint8_t* msgData)
{
    uint32_t regValue = regBase->CTRL0 & (~(CAN_CTRL0_TSNEXT_Msk | CAN_CTRL0_TSALL_Msk | CAN_CTRL0_TSONE_Msk | CAN_CTRL0_TPE_Msk));
    uint32_t control = 0U;
    volatile uint32_t* can_id = &regBase->TBUFF[0];
    volatile uint32_t* can_control = &regBase->TBUFF[1];
    volatile uint32_t* can_data = &regBase->TBUFF[2];

    regValue &= ~CAN_CTRL0_TBSEL_Msk;

    if (type == CAN_PRIMARY_TRANSMIT_BUFFER) {
        regValue |= CAN_CTRL0_TBSEL(0U);
    } else {
        regValue |= CAN_CTRL0_TBSEL(1U);
    }

    regBase->CTRL0 = regValue;

    if ((regBase->ACFCTRL0 & CAN_ACFCTRL0_TIMEEN_Msk) >> CAN_ACFCTRL0_TIMEEN_Pos == 1) {
        msgId |= CAN_TBUFF_TTSEN(1);
    }

    *can_id = msgId;
    control |= CAN_TBUFF_IDE((info->IDE == 1U) ? 1U : 0U);
    control |= CAN_TBUFF_RTR((info->RTR == 1U) ? 1U : 0U);
    control |= CAN_TBUFF_FDF((info->FDF == 1U) ? 1U : 0U);
    control |= CAN_TBUFF_BRS((info->BRS == 1U) ? 1U : 0U);
    control |= CAN_TBUFF_DLC(CAN_REG_ComputeDLCValue(info->dataLength));
    *can_control = control;

    if ((NULL != msgData) && (info->RTR == 0)) {
        for (uint32_t i = 0U; i < info->dataLength; i += 4U) {
            uint32_t x = CAN_REG_CombineU8ToU32(&msgData[i]);
            can_data[i >> 2U] = x;
        }
    }

    if (type != CAN_PRIMARY_TRANSMIT_BUFFER) {
        regValue = regBase->CTRL0;
        regValue &= ~CAN_CTRL0_TSNEXT_Msk;
        regValue |= CAN_CTRL0_TSNEXT(1);
        regBase->CTRL0 = regValue;
    }
}

/*******************************************************************************
 *** Function Name : CAN_REG_ReadRxFifo
 *** Description   : Read RBUFF
 ******************************************************************************/
void CAN_REG_ReadRxFifo(CAN_Type* regBase, can_msgbuff_t* data)
{
#define SAFE_READ_CHECK_MAX_COUNT 16

    uint32_t regValue = 0;
    const volatile uint32_t* can_data = &regBase->RBUFF[2];

    uint32_t* dataBuf = (uint32_t*)(void*)&data->data[0];
    volatile uint32_t readData = 0;
    volatile uint32_t safetyCheck = 0;
    uint32_t safetyCheckCount = 0;
    uint32_t regNum = 0;
    uint32_t flag;

    do {
        flag = System_SuspendAllInterrupts();   /*disable all interrupts*/
        CAN_REG_SafetyRead(&readData, &safetyCheck, &regBase->RBUFF[1]);
        System_ResumeAllInterrupts(flag);        /*enable all interrupts*/

        if (readData == safetyCheck) {
            data->cs = readData;
            break;
        }

        safetyCheckCount++;
    } while (safetyCheckCount < SAFE_READ_CHECK_MAX_COUNT);

    safetyCheckCount = 0;

    do {
        flag = System_SuspendAllInterrupts();   /*disable all interrupts*/
        CAN_REG_SafetyRead(&readData, &safetyCheck, &regBase->RBUFF[0]);
        System_ResumeAllInterrupts(flag);        /*enable all interrupts*/

        if (readData == safetyCheck) {
            break;
        }

        safetyCheckCount++;
    } while (safetyCheckCount < SAFE_READ_CHECK_MAX_COUNT);

    if (((data->cs & CAN_RBUFF_IDE_Msk) >> CAN_RBUFF_IDE_Pos) == 1U) {
        data->msgId = (readData & CAN_RBUFF_ID_EXT_Msk) >> CAN_RBUFF_ID_EXT_Pos;
    } else {
        data->msgId = (readData & CAN_RBUFF_ID_STD_Msk) >> CAN_RBUFF_ID_STD_Pos;
    }

    safetyCheckCount = 0;

    if ((data->cs & CAN_RBUFF_RTR_Msk) >> CAN_RBUFF_RTR_Pos == 0U) {
        data->dataLen = CAN_REG_Computepayloadsize((data->cs & CAN_RBUFF_DLC_Msk) >> CAN_RBUFF_DLC_Pos);

        if (((data->cs & CAN_RBUFF_FDF_Msk) >> CAN_RBUFF_FDF_Pos == 0) && (data->dataLen > 8)) {
            data->dataLen = 8;
        }

        for (uint32_t readBytes = 0; readBytes < data->dataLen;) {
            regNum = readBytes >> 2U;
            flag = System_SuspendAllInterrupts();   /*disable all interrupts*/
            CAN_REG_SafetyRead(&readData, &safetyCheck, &can_data[regNum]);
            System_ResumeAllInterrupts(flag);        /*enable all interrupts*/

            if (readData == safetyCheck) {
                dataBuf[regNum] = readData;
                readBytes += 4;
            } else {
                safetyCheckCount++;
            }

            if (safetyCheckCount > SAFE_READ_CHECK_MAX_COUNT) {
                break;
            }
        }
    } else {
        data->dataLen = 0;
    }

    regValue = regBase->CTRL0;
    regValue &= ~CAN_CTRL0_RREL_Msk;
    regValue |= CAN_CTRL0_RREL(1);
    regBase->CTRL0 = regValue;
}

#if defined (__CC_ARM) && defined (__ARMCC_VERSION) && ((__ARMCC_VERSION >= 5000000) && (__ARMCC_VERSION < 6000000))
#pragma O0
static inline void CAN_REG_SafetyRead(volatile uint32_t* readData, volatile uint32_t* safetyCheck, const volatile uint32_t* RbuffAddr)
{
    *readData = *RbuffAddr;
    __ISB();
    *safetyCheck = *RbuffAddr;
}
#elif defined (__clang__) && defined (__ARMCC_VERSION) && (__ARMCC_VERSION >= 6010050)
static inline __attribute__((optnone))void CAN_REG_SafetyRead(volatile uint32_t* readData, volatile uint32_t* safetyCheck, const volatile uint32_t* RbuffAddr)
{
    *readData = *RbuffAddr;
    *safetyCheck = *RbuffAddr;
}
#elif defined (__ICCARM__)
#pragma optimize = none
static inline void CAN_REG_SafetyRead(volatile uint32_t* readData, volatile uint32_t* safetyCheck, const volatile uint32_t* RbuffAddr)
{
    *readData = *RbuffAddr;
    __ISB();
    *safetyCheck = *RbuffAddr;
}
#elif defined (__GNUC__) && !defined (__clang__)
#pragma GCC optimize ("O0")
static inline void CAN_REG_SafetyRead(volatile uint32_t* readData, volatile uint32_t* safetyCheck, const volatile uint32_t* RbuffAddr)
{
    *readData = *RbuffAddr;
    *safetyCheck = *RbuffAddr;
}
#else
#error Not supported compiler type
#endif

/*******EOF********************************************************************/
